# -*- coding: utf-8 -*-
import nls
import json
import os
import time
import threading
#import #logging
import pyaudio
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest

# Configure #logging to be active
#logging.basicConfig(level=#logging.CRITICAL, format='%(asctime)s - %(levelname)s - %(message)s', encoding='utf-8')

# Audio configuration
FORMAT = pyaudio.paInt16 # 16-bit resolution
CHANNELS = 1             # 1 channel
RATE = 16000             # 16000 samples per second

class AliTTSAgent:
    def __init__(self):
        self.__audio_stream = None
        nls.enableTrace(False) # Enable detailed NLS #logging for debugging
        self.__appkey, self.__access_id, self.__access_key = self._get_ali_keys_from_settings()
        if not all([self.__appkey, self.__access_id, self.__access_key]):
            #logging.error("Aliyun TTS credentials are not fully configured. Please check settings.txt.")
            raise ValueError("Aliyun TTS credentials missing.")

        self.__url = "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1"
        self.__token = self._get_sts_token()
        if not self.__token:
            #logging.error("Failed to obtain STS Token. Cannot initialize AliTTSAgent.")
            raise ConnectionError("Failed to obtain Aliyun STS Token.")

        self.__pyaudio = pyaudio.PyAudio()
        #logging.info(f"AliTTSAgent initialized with appkey: {self.__appkey[:4]}...")

    def _get_ali_keys_from_settings(self):
        # Path is relative to this file's location, going up two directories to find settings.txt
        settings_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'settings.txt')
        #logging.info(f"Attempting to read settings from {settings_path}")
        try:
            with open(settings_path, 'r', encoding='utf-8') as f:
                settings = json.load(f)
                app_key = settings.get("Ali_voice_app_key")
                access_id = settings.get("Ali_voice_access_id")
                access_key = settings.get("Ali_voice_access_key")
                return app_key, access_id, access_key
        except Exception as e:
            #logging.error(f"An unexpected error occurred while reading settings.txt: {e}")
            return None, None, None

    def _get_sts_token(self):
        try:
            client = AcsClient(self.__access_id, self.__access_key, "cn-shanghai")
            request = CommonRequest()
            request.set_method('POST')
            request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
            request.set_version('2019-02-28')
            request.set_action_name('CreateToken')
            response = client.do_action_with_exception(request)
            jss = json.loads(response)
            if 'Token' in jss and 'Id' in jss['Token']:
                token = jss['Token']['Id']
                #logging.info(f"Successfully obtained STS Token.")
                return token
            else:
                pass
                #logging.error(f"Failed to get STS Token: {jss}")
                return None
        except Exception as e:
            #logging.error(f"Error getting STS Token: {e}")
            return None

    def __on_metainfo(self, message, *args):
        pass
        #logging.info(f"TTS metainfo received: {message}")

    def __on_error(self, message, *args):
        #logging.error(f"TTS Error from NLS: {message}")
        self.__error = True
        self.__completed.set()

    def __on_close(self, *args):
        # try:
        #     if self.__audio_stream and self.__audio_stream.is_active():
        #         self.__audio_stream.stop_stream()
        #         self.__audio_stream.close()
        # except Exception as e:
        #     pass
            #logging.error(f"Error closing audio stream: {e}")
        #logging.info("TTS connection closed by NLS.")
        # This callback is triggered by the NLS SDK thread.
        # We set the event to signal completion, but cleanup is handled in the main thread's finally block.
        self.__completed.set()

    def __on_data(self, data, *args):
        # This callback writes audio data to the stream.
        if self.__audio_stream:
            try:
                self.__audio_stream.write(data)
            except Exception as e:
                #logging.error(f"Error writing data to audio stream: {e}")
                self.__error = True
                self.__completed.set() # Signal completion on error

    def __on_completed(self, message, *args):
        #logging.info(f"TTS synthesis completed message from NLS: {message}")
        # This signals that the server has sent all data.
        # The __on_close callback will be called shortly after.
        self.__completed.set()

    def synthesize_and_play(self, text, voice="xiaoyun", aformat="pcm", sample_rate=RATE, volume=50, speech_rate=0, pitch_rate=0):
        if not self.__token:
            #logging.error("STS Token is not available. Cannot synthesize speech.")
            return False
        #logging.info(f"Attempting to synthesize and play: '{text[:30]}...'")
        # if not self.__audio_stream  is None:
        #     self.__audio_stream.stop_stream()
        #     self.__audio_stream.close
        # Reset state for this run
        if self.__audio_stream is None:
            self.__completed = threading.Event()
            self.__error = False
            self.__audio_stream = None

            # 1. Open PyAudio stream
            try:
                self.__audio_stream = self.__pyaudio.open(format=FORMAT,
                                                        channels=CHANNELS,
                                                        rate=sample_rate,
                                                        output=True)
                #logging.info(f"PyAudio stream opened successfully (Rate: {sample_rate}).")
            except Exception as e:
                #logging.error(f"FATAL: Failed to open PyAudio stream: {e}")
                return False

        
        # 2. Create NLS instance
        tts = nls.NlsSpeechSynthesizer(
            url=self.__url,
            token=self.__token,
            appkey=self.__appkey,
            on_metainfo=self.__on_metainfo,
            on_data=self.__on_data,
            on_completed=self.__on_completed,
            on_error=self.__on_error,
            on_close=self.__on_close
        )

        # 3. Run synthesis, ensuring cleanup happens with a finally block
        try:
            #logging.info("Calling tts.start() (blocking call)...")
            # This is a blocking call that will not return until on_completed and on_close have been called.
            tts.start(
                text=text,
                voice=voice,
                aformat=aformat,
                sample_rate=sample_rate,
                volume=volume,
                speech_rate=speech_rate,
                pitch_rate=pitch_rate,
                wait_complete=True
            )
            #logging.info("tts.start() has completed.")
        except Exception as e:
            #logging.error(f"An exception occurred during the tts.start() call: {e}")
            self.__error = True
        finally:
            # This cleanup is critical and will run regardless of success or failure.
            #logging.info("Entering cleanup block (finally).")
            
            # Shutdown the NLS connection.
            #logging.info("Shutting down NLS speech synthesizer...")
            tts.shutdown()
            
            # Close the audio stream if it's still open.
            if self.__audio_stream and self.__audio_stream.is_active():
                #logging.info("Stopping and closing PyAudio stream...")
                self.__audio_stream.stop_stream()
                self.__audio_stream.close()
            self.__audio_stream = None
            #logging.info(f"Cleanup finished. Final error status: {self.__error}")

        return not self.__error

    def __del__(self):
        # This will be called when the agent object is garbage collected.
        if self.__pyaudio:
            #logging.info("Terminating PyAudio instance.")
            self.__pyaudio.terminate()

# Example usage for direct testing of this file
if __name__ == "__main__":
    try:
        agent = AliTTSAgent()
        
        text_to_play_1 = "你好，这是一个经过重构和修复的实时语音播放测试。欢迎使用阿里云智能语音服务。"
        #logging.info("--- Running Example 1 (Real-time Playback) ---")
        success_1 = agent.synthesize_and_play(text_to_play_1)
        # if success_1:
        #     #logging.info("Example 1: Speech synthesis and playback completed successfully.")
        # else:
            #logging.error("Example 1: Speech synthesis and playback failed.")

        time.sleep(1)

        text_to_play_2 = "现在测试第二条语音，使用不同的声音和语速。"
        #logging.info("--- Running Example 2 (Different Voice and Speed) ---")
        success_2 = agent.synthesize_and_play(text_to_play_2, voice="ruoxi", speech_rate=100)
        # if success_2:
        #     #logging.info("Example 2: Speech synthesis and playback completed successfully.")
        # else:
            #logging.error("Example 2: Speech synthesis and playback failed.")

    except (ValueError, ConnectionError) as e:
        pass
        #logging.error(f"Agent initialization failed: {e}")
    except Exception as e:
        pass
        #logging.error(f"An unexpected error occurred during example execution: {e}")
